/*
 * @Author: zwf 240970521@qq.com
 * @Date: 2023-07-14 23:07:31
 * @LastEditors: zwf 240970521@qq.com
 * @LastEditTime: 2023-08-16 22:49:06
 * @FilePath: /esp32_cmake_test/pwm/pwm.c
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#include "pwm.h"
#include "pin.h"
#include "esp_log.h"
#include "esp_attr.h"

/*
占空比分辨率和pwm频率的关系
40M->0
20M->1
5M->2
2.5M->3
1.25M->4
625k->5
312.5k->6
156.25->7
78.125->8
*/

typedef struct
{
    int              isInit;
    ledc_mode_t      speedMode;
    int              freq;
    ledc_timer_bit_t dutyResolution;
} TimerStatus;

typedef struct
{
    /*前SOC_LEDC_CHANNEL_NUM代表low speed，后面的代表high speed*/
    int         pwmChannel[SOC_LEDC_CHANNEL_NUM * 2];
    TimerStatus timerStatus[LEDC_TIMER_MAX];
} PwmManager;

typedef struct
{
    int         channel;
    int         pin;
    ledc_mode_t speedMode;
} PwmObj;

static char      *tag = "pwm";
static PwmManager sgPwmManager;

/*定时器配置*/
static esp_err_t PWM_timerConfig(PwmCfg cfg)
{
    TimerStatus        *timer;
    ledc_timer_config_t ledcTimer;

    if (cfg.timer >= LEDC_TIMER_MAX)
    {
        ESP_LOGE(tag, "timer[%d] is invalid\n", cfg.timer);
        return ESP_FAIL;
    }

    timer = &sgPwmManager.timerStatus[cfg.timer];
    if (timer->isInit)
    {
        if (timer->dutyResolution != cfg.dutyResolution || timer->freq != cfg.freq || timer->speedMode != cfg.speedMode)
        {
            ESP_LOGE(tag, "timer[%d] is inited\n", cfg.timer);
            return ESP_FAIL;
        }
        /*已经配置过，不需要再配置*/
        return ESP_OK;
    }

    ledcTimer.timer_num       = cfg.timer;
    ledcTimer.clk_cfg         = LEDC_AUTO_CLK;
    ledcTimer.duty_resolution = cfg.dutyResolution;
    ledcTimer.freq_hz         = cfg.freq;
    ledcTimer.speed_mode      = cfg.speedMode;
    ledcTimer.deconfigure     = 0;

    if (ledc_timer_config(&ledcTimer))
    {
        ESP_LOGE(tag, "timer[%d] config fail\n", cfg.timer);
        return ESP_FAIL;
    }
    return ESP_OK;
}

/*删除pwm设备*/
void PWM_delete(PwmDev *dev)
{
    PwmObj *priv  = NULL;
    int    *group = NULL;

    if (!dev)
    {
        return;
    }

    priv = dev->priv;
    if (priv)
    {
        PIN_release(priv->pin);
        group = (priv->speedMode == LEDC_LOW_SPEED_MODE) ? &sgPwmManager.pwmChannel[0] : &sgPwmManager.pwmChannel[8];
        /*清0*/
        group[priv->channel] = 0;
        free(priv);
    }
    free(dev);
}

static esp_err_t IRAM_ATTR PWM_setDuty(PwmDev *dev, int duty)
{
    PwmObj *priv = NULL;

    if (!dev)
    {
        return ESP_FAIL;
    }

    priv = dev->priv;

    if (ledc_set_duty(priv->speedMode, priv->channel, duty))
    {
        ESP_LOGE(tag, "pwm[%d] set duty fail\n", priv->channel);
        return ESP_FAIL;
    }

    return ledc_update_duty(priv->speedMode, priv->channel);
}

static uint32_t PWM_getDuty(PwmDev *dev)
{
    PwmObj *priv = NULL;

    if (!dev)
    {
        return ESP_FAIL;
    }

    priv = dev->priv;

    return ledc_get_duty(priv->speedMode, priv->channel);
}

esp_err_t PWM_create(PwmCfg cfg, PwmDev **dev)
{
    PwmObj               *priv = NULL;
    PwmDev               *pwm  = NULL;
    ledc_channel_config_t ledcChannel;
    int                  *group = NULL, i = 0;

    if (!dev)
    {
        return ESP_FAIL;
    }

    /*注册管脚*/
    if (PIN_register(cfg.pin, PIN_PWM))
    {
        ESP_LOGE(tag, "pin[%d] register fail\n", cfg.pin);
        return ESP_FAIL;
    }

    pwm = calloc(1, sizeof(PwmDev));
    if (!pwm)
    {
        ESP_LOGE(tag, "calloc pwm fail\n");
        goto free;
    }
    priv = calloc(1, sizeof(PwmObj));
    if (!priv)
    {
        ESP_LOGE(tag, "calloc pwm->priv fail\n");
        goto free;
    }

    pwm->priv = priv;
    priv->pin = cfg.pin;
    /*配置定时器*/
    if (PWM_timerConfig(cfg))
    {
        ESP_LOGE(tag, "pwm timer config fail\n");
        goto free;
    }

    /*配置pwm*/
    ledcChannel.speed_mode = cfg.speedMode;
    ledcChannel.timer_sel  = cfg.timer;
    ledcChannel.intr_type  = LEDC_INTR_DISABLE;
    ledcChannel.gpio_num   = cfg.pin;
    ledcChannel.duty       = 0;
    ledcChannel.hpoint     = 0;
    /*寻找空闲pwm通道*/
    group = (cfg.speedMode == LEDC_LOW_SPEED_MODE) ? &sgPwmManager.pwmChannel[0] : &sgPwmManager.pwmChannel[8];
    for (i = 0; i < SOC_LEDC_CHANNEL_NUM; i++)
    {
        if (!group[i])
        {
            ledcChannel.channel = i;
            group[i]            = cfg.pin + 1;
            break;
        }
    }

    if (i == SOC_LEDC_CHANNEL_NUM)
    {
        ESP_LOGE(tag, "pwm is full\n");
        goto free;
    }
    priv->speedMode = cfg.speedMode;
    priv->channel   = ledcChannel.channel;

    if (ledc_channel_config(&ledcChannel))
    {
        ESP_LOGE(tag, "pwm[%d] config fail\n", priv->channel);
        goto free;
    }

    pwm->setDuty = PWM_setDuty;
    pwm->getDuty = PWM_getDuty;
    *dev         = pwm;

    return ESP_OK;
free:
    PWM_delete(pwm);
    return ESP_FAIL;
}
